﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interactivity;
using System.Windows.Media;
using FirstFloor.Documents;
using FirstFloor.Documents.Controls;
using FirstFloor.Documents.Controls.Behaviors;
using SLMitsuControls;

namespace SampleBrowser.Behaviors
{
    /// <summary>
    /// Provides page navigation and implements the IDataProvider interface required by the book control.
    /// </summary>
    public class BookBehavior
        : Behavior<UCBook>, IDataProvider, ITextContainer
    {
        /// <summary>
        /// Identifies the DocumentDataSource dependency property.
        /// </summary>
        public static readonly DependencyProperty DocumentDataSourceProperty = DependencyProperty.Register("DocumentDataSource", typeof(DocumentDataSource), typeof(BookBehavior), new PropertyMetadata(new PropertyChangedCallback(OnDataSourceChanged)));
        /// <summary>
        /// Identifies the PageNumber dependency property.
        /// </summary>
        public static readonly DependencyProperty PageNumberProperty = DependencyProperty.Register("PageNumber", typeof(int), typeof(BookBehavior), new PropertyMetadata(new PropertyChangedCallback(OnPageNumberChanged)));

        private VirtualizingPageCollection pageCollection;
        private List<FrameworkElement> pageControls = new List<FrameworkElement>();
        private TextSelection selection;

        /// <summary>
        /// Initializes a new instance of the <see cref="BookBehavior"/> class.
        /// </summary>
        public BookBehavior()
        {
            this.selection = new TextSelection() {
                IsEnabled = true
            };
        }

        private static void OnDataSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((BookBehavior)o).OnDataSourceChanged((DocumentDataSource)e.OldValue, (DocumentDataSource)e.NewValue);
        }

        private static void OnPageNumberChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((BookBehavior)o).OnPageNumberChanged((int)e.OldValue, (int)e.NewValue);
        }

        private void OnDataSourceChanged(DocumentDataSource oldValue, DocumentDataSource newValue)
        {
            // release old event handler
            if (this.pageCollection != null) {
                this.pageCollection.CollectionChanged -= OnPageCollectionChanged;
            }

            if (newValue != null) {
                this.pageCollection = newValue.CreatePageCollection();
                this.pageCollection.CollectionChanged += OnPageCollectionChanged;

                ResetBook();
            }
        }

        private void OnPageNumberChanged(int oldValue, int newValue)
        {
            if (newValue == -1) {
                return;
            }
            bool onePageFlip = Math.Abs(oldValue - newValue) == 1;

            Debug.WriteLine("OnPageNumberChanged({0}, {1})", oldValue, newValue);
            var oldSheet = oldValue / 2;
            var newSheet = newValue / 2;
            if (onePageFlip && newSheet == oldSheet - 1) {
                this.AssociatedObject.AnimateToPreviousPage(500);
            }
            else if (onePageFlip && newSheet == oldSheet + 1) {
                this.AssociatedObject.AnimateToNextPage(500);
            }
            else if (oldSheet != newSheet) {
                this.AssociatedObject.CurrentSheetIndex = newSheet;
            }
        }

        private void OnPageCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            ResetBook();
        }

        private void ResetBook()
        {
            if (this.pageControls == null) {
                return;
            }
            // properly dispose of FixedPageViewer
            foreach (Border border in this.pageControls) {
                var pageViewer = (FixedPageViewer)border.Child;
                pageViewer.FixedPage = null;
            }

            this.pageControls.Clear();

            // create a FixedPageViewer control for each page
            if (this.pageCollection != null) {
                foreach (var pageContext in this.pageCollection) {
                    var pageViewer = new FixedPageViewer() {
                        Scale = null        //auto-scale
                    };
                    pageViewer.SetBinding(FixedPageViewer.FixedPageProperty, new Binding("FixedPage"));
                    pageViewer.SetBinding(FixedPageViewer.FixedPageSizeProperty, new Binding("FixedPageSize"));
                    pageViewer.SetBinding(FixedPageViewer.FixedPageErrorProperty, new Binding("FixedPageError"));

                    // attach navigation behavior
                    var behavior = new NavigationBehavior();
                    behavior.TextContainer = this;
                    behavior.Attach(pageViewer);

                    // attach FixedPageAdorner behavior to render text selection
                    var adorner = new FixedPageAdorner();
                    adorner.Selection = this.selection;
                    adorner.Attach(pageViewer);

                    var container = new Border() {
                        Background = new SolidColorBrush(Colors.White),
                        BorderBrush = new SolidColorBrush(Color.FromArgb(0xFF, 0x9D, 0x9D, 0x9D)),
                        BorderThickness = new Thickness(1),
                        Child = pageViewer,
                        DataContext = pageContext,
                        HorizontalAlignment = HorizontalAlignment.Center,
                        VerticalAlignment = VerticalAlignment.Center
                    };

                    this.pageControls.Add(container);
                }
            }

            this.AssociatedObject.SetData(this);
        }

        /// <summary>
        /// Gets or sets the document data source.
        /// </summary>
        /// <value>The data source.</value>
        public DocumentDataSource DocumentDataSource
        {
            get { return (DocumentDataSource)GetValue(DocumentDataSourceProperty); }
            set { SetValue(DocumentDataSourceProperty, value); }
        }

        /// <summary>
        /// Gets or sets the page number.
        /// </summary>
        /// <value>The page number.</value>
        public int PageNumber
        {
            get { return (int)GetValue(PageNumberProperty); }
            set { SetValue(PageNumberProperty, value); }
        }

        /// <summary>
        /// Called after the behavior is attached to an AssociatedObject.
        /// </summary>
        /// <remarks>Override this to hook up functionality to the AssociatedObject.</remarks>
        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.OnPageTurned += OnPageTurned;
        }

        /// <summary>
        /// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
        /// </summary>
        /// <remarks>Override this to unhook functionality from the AssociatedObject.</remarks>
        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.OnPageTurned -= OnPageTurned;
        }

        private void OnPageTurned(int leftPageIndex, int rightPageIndex)
        {
            if (rightPageIndex != -1) {
                this.PageNumber = rightPageIndex;
            }
            else {
                this.PageNumber = leftPageIndex;
            }
        }

        /// <summary>
        /// Gets the total number of pages.
        /// </summary>
        /// <returns></returns>
        public int GetCount()
        {
            return this.pageControls.Count;
        }

        /// <summary>
        /// Gets the page at specified 0-based index.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <returns></returns>
        public object GetItem(int index)
        {
            var pageControl = this.pageControls.ElementAtOrDefault(index);
            if (pageControl != null) {
                var pageContext = (PageContext)pageControl.DataContext;
                pageContext.IsInUse = true;     // ensure page is loaded
            }

            foreach(var pageContext in this.pageCollection){
                var pageIndex = pageContext.PageContent.PageNumber - 1;

                if (pageIndex < index - 5 || pageIndex > index + 3) {
                    // unload invisible pages
                    pageContext.IsInUse = false;
                }
            }

            DebugPrintPagesInUse();

            return pageControl;
        }

        private void DebugPrintPagesInUse()
        {
            var b = new StringBuilder();
            foreach (var pageContext in this.pageCollection) {
                if (pageContext.IsInUse) {
                    if (b.Length > 0) {
                        b.Append(", ");
                    }
                    b.Append(pageContext.PageContent.PageNumber);
                }
            }
            Debug.WriteLine("Pages in use: {0}", b);
        }

        /// <summary>
        /// Brings the specified link target into view.
        /// </summary>
        /// <param name="linkTarget">The link target.</param>
        public void BringIntoView(LinkTarget linkTarget)
        {
            this.PageNumber = linkTarget.Page.PageNumber;
        }

        /// <summary>
        /// Brings the specified text position into view.
        /// </summary>
        /// <param name="position">The position.</param>
        public void BringIntoView(FirstFloor.Documents.TextPointer position)
        {
            this.PageNumber = position.PageNumber;
        }

        /// <summary>
        /// Gets the text.
        /// </summary>
        /// <param name="position1">The position1.</param>
        /// <param name="position2">The position2.</param>
        /// <returns></returns>
        public string GetText(ref FirstFloor.Documents.TextPointer position1, ref FirstFloor.Documents.TextPointer position2)
        {
            // not implemented
            return null;
        }

        /// <summary>
        /// Called when the annotation service has changed.
        /// </summary>
        public void OnAnnotationServiceChanged()
        {
            // not implemented
        }

        /// <summary>
        /// Gets the selection.
        /// </summary>
        /// <value>The selection.</value>
        public FirstFloor.Documents.TextSelection Selection
        {
            get { return this.selection; }
        }
    }
}
